﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace Marvin.Tests
{
    /// <summary>
    /// This class is a simple csv parser for building up the training set. 
    /// I'm not sure whether this class will be added to the library or not. 
    /// It will be used for testing purposes. 
    /// </summary>
    public class CsvTrainingDataParser
    {
        #region Fields
        private const char Separator = ',';
        private readonly int _numberOfFeatures;
        #endregion

        /// <summary>
        /// Creates a new parser for a supplied number of features. 
        /// </summary>
        /// <param name="numberOfFeatures">Number of features in the training set</param>
        public CsvTrainingDataParser(int numberOfFeatures)
        {
            _numberOfFeatures = numberOfFeatures;
        }

        #region Properties
        /// <summary>
        /// The dimensions of the training set
        /// </summary>
        public int NumberOfFeatures
        {
            get { return _numberOfFeatures; }
        }
        #endregion

        #region Behaviour
        /// <summary>
        /// Parses lines of csv data to create a training set
        /// Lines that do not comply to the number of features are skipped
        /// </summary>
        /// <param name="linesOfCsvFile">Set of strings where each string is one line of the csv file</param>
        /// <returns>A training set with the data extracted from the lines</returns>
        public ITrainingSet<double> Parse(IEnumerable<string> linesOfCsvFile)
        {
            if(linesOfCsvFile == null)
                throw new ArgumentException("linesOfCsvFile parameter must be initialized.");

            var lines = linesOfCsvFile as IList<string> ?? linesOfCsvFile.ToList();
 

            var trainingSet = new TrainingSet<double>(NumberOfFeatures, lines.Count);
            foreach (var line in lines)
            {
                var trainingExample = ExtractTrainingExample(line);
                if (trainingExample == null)
                    continue;

                if (trainingExample.Length < 2)
                    // The line must at least have 2 values (1 input, 1 output)
                    continue;

                var inputData = new double[trainingExample.Length - 1];
                Array.Copy(trainingExample, inputData, trainingExample.Length - 1);

                var outputData = trainingExample[trainingExample.Length - 1];

                trainingSet.Add(inputData, outputData);
            }
            return trainingSet; 
        }
        #endregion

        #region Private Methods
        private double[] ExtractTrainingExample(string line)
        {
            var valuesToParse = NumberOfFeatures + 1; // 1 output value

            var stringValues = line.Split(Separator);
            var parsedValues = new double[valuesToParse];

            if (stringValues.Length != valuesToParse)
                return null;

            for (int currentFeature = 0; currentFeature < valuesToParse; currentFeature++)
            {
                var parsedValue = ParseValue(stringValues[currentFeature]);

                if (! parsedValue.HasValue)
                    return null; 

                parsedValues[currentFeature] = parsedValue.Value; 
            }
            return parsedValues;
        }

        private static double? ParseValue(string stringValue)
        {
            double parsedValue;
            
            if (!double.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out parsedValue))
                return null; 

            return parsedValue;
        }

        #endregion
    }
}
